<?php
/**
 * Created by PhpStorm.
 * User: guokunsheng
 * Date: 2018/6/6
 * Time: 17:35
 */

namespace app\api\controller\v13;

use app\common\cachemodel\AppConfig;
use app\common\cachemodel\AppLoginLog;
use app\common\cachemodel\AppQq;
use app\common\cachemodel\AppWeibo;
use app\common\cachemodel\MiniAppUser;
use app\common\cachemodel\Wechat;
use app\common\cachemodel\ZlRecordShareAdd;
use app\common\cachemodel\ZlUserZx;
use app\common\lib\Alidayu;
use app\common\lib\exception\ApiException;
use app\common\lib\IAuth;
use app\common\lib\Aes;
use app\common\lib\Util;
use app\common\model\AppTelLogin;
use app\common\model\User;
use Exception;
use GuzzleHttp\Exception\GuzzleException;
use think\Cache;
use \think\Db;
use think\Log;
use think\response\Json;

/**
 * @title 登录相关接口
 * @desc 账号密码登录 <br/>手机号验证码登录<br/>微信登录<br/>QQ登录<br/>微博登录<br/>
 * @version 2.0.0
 * @url /api/v2/login/...
 * @readme /doc/api/login/login.md
 */
class Login extends Auth2
{
    protected $restActionList = ['qq', 'weibo'];

    /**
     * @title 手机验证码登录
     * @method POST
     * @return array
     * @throws ApiException
     */
    public function mobile()
    {
        if (!request()->isPost())
        {
            return show(config('code.error'), '未知操作');
        }

        $param = $this->request->only(['tel', 'code', 'password']);
        $time = $this->request->time();
        $ip = $this->request->ip();
        $appConfig = config('app');

        // 手机号不可注册
        $user = $this->CheckUserTel($param['tel']);

        // 短信验证码 或 密码是否存在
        if (empty($param['code']) && empty($param['password']))
        {
            //$param['code'] = Aes::decrypt($param['code']); // 1234
            return show(config('code.error'), '手机短信验证码或者密码不合法');
        }
        // 短信登录时，检测验证码是否正确
        if (!empty($param['code']))
        {
            if (!in_array($param['tel'], $appConfig['mobile'])
                || $param['code'] != substr($appConfig['code'] * (substr($param['tel'], 3, 6)), 0, 6))
            {
                //苹果和安卓应用商店审核账号，固定验证码为666666
                if (($param['tel'] == '13045794520' && $param['code'] == '666666')
                    || ($param['tel'] == '13261877781' && $param['code'] == '666666'))
                {

                } else
                {
                    //  validate 严格校验
                    $code = Alidayu::getInstance()->checkSmsIdentify($param['tel']);

                    if ($code != $param['code'])
                    {
                        return show(config('code.error'), '短信验证码错误');
                    }
                }
            }
        }
        // 生成token  和 token到期时间
        $token = IAuth::setAppLoginToken($param['tel']);
        $data = [
            'token' => $token,
            'token_expire' => strtotime("+" . $appConfig['login_time_out_day'] . " days"),
        ];
        $UserModel = new User();
        //判断用户是否绑定准了号
        if ($user)
        {
            $UserModel->check_uen($user);
        }

        $nickname = preg_replace('/(1[3456789]{1}[0-9])[0-9]{4}([0-9]{4})/i', '$1****$2', $param['tel']);

        // 该用户是否正常  forbidden==1 禁止登录
        if ($user && $user['forbidden'] == 0)
        {
            // 已过期重新生成access_token
            if ($time > $user['token_expire'] || !in_array($param['tel'], $appConfig['mobile']))
            {
                $UserModel->save($data, ['tel' => $param['tel']]);
            } else
            {
                $token = $user['token'];
            }
//            $UserModel->save($data, ['tel' => $param['tel']]);
            if (!$user['nickname'])
            {
                try
                {
                    Db::table('app_user')->where(['tel' => $param['tel']])
                        ->update(['nickname' => $nickname]);
                }
                catch (Exception $e)
                {
                    Log::write('手机验证码登陆时初始化用户昵称失败' . $e->getMessage(), true);
                }
            }
        } elseif ($user && $user['forbidden'] == 1)
        {
            return show(config('code.error'), '该账号禁止登录');
        } //手机号不可注册
        elseif (!$user && !empty($param['code']) && !empty($param['tel']))
        {
            // 第一次登录 注册数据
            $user['uen'] = substr(md5($this->request->time(true) . mt_rand(0, 1000000) . uniqid()), 0, 12);
            $user['nickname'] = $nickname;
            $user['reg_time'] = $time;
            $user['reg_ip'] = $ip;
            $user['last_login'] = $time;
            $user['last_ip'] = $ip;
            $user['last_active'] = $time;
            $user['status'] = 1;
            $user['sex'] = 1;
            $user['tel'] = $param['tel'];
            $user['is_first_login'] = 1;
            $user['uc_id'] = md5($param['tel'] . $this->request->time(true));
            $user['token'] = $data['token'];
            $user['token_expire'] = $data['token_expire'];
            $user['has_self_recode'] = 0;
            $user['channel'] = Util::getChannelId();
            try
            {
                $user['id'] = $UserModel->insertGetId($user);
                // 记录注册日志
                Util::logRealTimeAnalyzeWrite(1, [
                    'user_uid' => $user['id'],
                    'user_name' => $param['tel'],
                    'user_sex' => 0,
                    'user_reg_type' => 4,
                    'user_ip' => $ip
                ]);
            }
            catch (\Exception $e)
            {
                return show(config('code.error'), '注册失败,请稍后重试');
            }

            // 判断用户 APP版本大于等于 2.4.0 赠送 新手礼包
            if (Util::CheckAppVersion('2.4.0') !== '小')
            {   // 不小于 2.4.0
                Util::queuePush('register_package', ['uid' => (int)$user['id']], 0);
            }

            try
            {
                $msg = Db::table('app_message_center')->field('id')->where('range', 0)->select();
            }
            catch (\Exception $e)
            {
                \think\Log::write('获取系统消息失败' . $e->getMessage(), true);
            }
            foreach ($msg as $k => $v)
            {
                Db::table('app_user_msg')->insert(['uid' => $user['id'], 'mid' => $v['id']]);
            }
        }

        if ($user)
        {
            $id = (int)($user['id']);
            // 账号模拟
            $simulation = $appConfig['simulation'];

            if ($id === $simulation['from'] && isset($simulation['to']) && $simulation['to'])
            {
                $id = $simulation['to'];
            }

            $result = [
                'id' => $id,
                'has_self_recode' => $user['has_self_recode'],
                'user_access_token' => (new Aes())->encryptWithOpenssl($token . "||" . $user['id']),
                'uc_id' => $user['uc_id'],
            ];
            // 记录登陆日志
            Db::table('app_login_log')->insertGetId([
                'uid' => $user['id'],
                'login_time' => $time,
                'login_type' => 4,
                'login_ip' => $ip,
            ]);
            return show(config('code.success'), 'ok', $result);
        }
        return show(config('code.error'), '登录失败');
    }

    /**
     * 检测手机号格式，不可注册
     * @param $tel
     * @return array|\PDOStatement|string|\think\Model
     * @throws ApiException
     */
    private function CheckUserTel($tel)
    {
        // 手机号是否存在
        if (!preg_match("/^1[3456789]{1}\d{9}$/", $tel))
        {
            Log::write($tel . '手机号错误', 'error');
            throw new ApiException('手机号错误', 200, 0);
        }
        //判断手机号是否注册过
        try
        {
            $user = (new \app\common\cachemodel\User())->field('id,forbidden,has_self_recode,password,nickname,uc_id,avatar,token_expire,token,uen')->where(['tel' => $tel])->findOrFail()->toArray();
        }
        catch (Exception $e)
        {
            $user = [];
            // throw new ApiException('手机号未注册，请用三方登录', 200, 409);
        }
        return $user;
    }

    /**
     * 三方登录接口
     * @return Json
     * @throws ApiException
     * @throws GuzzleException
     */
    public function OuterLogin()
    {
        $param = input('param.param');
        $param = $param ? json_decode($param, true) : [];
        $type = input('param.type/d');
        switch ($type)
        {
            case 1:
                $res = $this->wechat($param);
                break;
            case  2:
                $res = $this->qq($param);
                break;
            case  3:
                $res = $this->weibo($param);
                break;
            default:
                return show(config('code.error'), '未知的登录方式');
                break;
        }
        //记录登录
        $this->LoginLog($res['id'], $type);
        return show(config('code.success'), '登录成功', $res);
    }

    /**
     * 微信登录
     * @param $param
     * @return array|Json
     * @throws ApiException
     * @throws GuzzleException
     * @throws Exception
     */
    private function wechat($param)
    {
        $header = request()->header('app-type');
        $WechatModel = new Wechat();
        if ($header == 'ios')
        {
            $wechat_user = $param;
        } else
        {
            $wechat_user = $WechatModel->WechatInfo($param);
        }
        //验证是否注册过
        $User = (new Wechat())->getWechatInfo($wechat_user['unionid']);
        if ($User)
        {
            return $this->LoginUser($User, $wechat_user);
        }
        //新增用户，qq和user表进行数据添加
        $checkResult = Util::registCheckLogoutCD(['unionid' => $wechat_user['unionid']]);
        if (!$checkResult['result'])
        {
            throw new ApiException($checkResult['msg'], 200, config('code.error'));
        }
        Db::startTrans();
        try
        {
            $miniAppInfo = (new MiniAppUser())->where(['mini_type' => 'WEAPP', 'unionid' => $wechat_user['unionid']])->where('status', '<>', 1)->find();
            if ($miniAppInfo)
            {
                if ($miniAppInfo['status'] == 2)
                {
                    throw new \Exception('该账户禁止登陆', config('code.error'));
                }
                if ($miniAppInfo['uid'])
                {
                    $uid = $miniAppInfo['uid'];
                } else
                {
                    $res = $this->RegUser($wechat_user, 1, $wechat_user['unionid']);
                    $uid = $res['id'];
                    (new MiniAppUser())->save(['uid' => $uid], ['id' => $miniAppInfo['id']]);
                }
            } else
            {
                $res = $this->RegUser($wechat_user, 1, $wechat_user['unionid']);
                $uid = $res['id'];
            }
            $wechat_insert = [
                'uid' => $uid,
                'nickname' => $wechat_user['nickname'],
                'sex' => $wechat_user['sex'],
                'city' => $wechat_user['city'],
                'province' => $wechat_user['province'],
                'country' => $wechat_user['country'],
                'headimgurl' => $wechat_user['headimgurl'],
                'openid' => $wechat_user['openid'],
                'unionid' => $wechat_user['unionid'],
                'create_time' => time(),
                'update_time' => time(),
            ];
            //qq表生成数据
            $WechatModel->SaveData($wechat_insert);
            Db::commit();
        }
        catch (Exception $e)
        {
            Log::write('用户注册失败：' . $e->getMessage() . '请求参数：' . json_encode($param, true) . '失败行数：' . $e->getLine(), 'error');
            Db::rollback();
            throw new ApiException('用户注册失败', 200, 0);
        }
        return $res;
    }

    /**
     * QQ登录
     * @param $param
     * @return array|Json
     * @throws ApiException
     * @throws GuzzleException
     * @throws Exception
     */
    private function qq($param)
    {
        $QqModel = new AppQq();
        // 获取用户qq信息
        $qq_user = $QqModel->QqInfo($param);
        // 验证是否注册过
        $User = (new AppQq())->getUserQqInfoFromCache($qq_user['unionid']);
        if ($User)
        {
            return $this->LoginUser($User, $qq_user);
        }
        // 新增用户，qq和user表进行数据添加
        $checkResult = Util::registCheckLogoutCD(['qq_unionid' => $qq_user['unionid']]);
        if (!$checkResult['result'])
        {
            throw new ApiException($checkResult['msg'], 200, config('code.error'));
        }
        Db::startTrans();
        try
        {
            $res = $this->RegUser($qq_user, 2, $qq_user['unionid']);
            $qq_insert = [
                'uid' => $res['id'],
                'openid' => $param['openid'],
                'unionid' => $qq_user['unionid'],
                'create_time' => time(),
                'nickname' => $qq_user['nickname'],
                'sex' => $qq_user['sex'],
                'avatar' => $qq_user['figureurl_qq_1'],
            ];
            //qq表生成数据
            (new AppQq())->SaveData($qq_insert);
            Db::commit();
        }
        catch (Exception $e)
        {
            Log::write('用户注册失败：' . $e->getMessage() . '请求参数：' . json_encode($param, true) . '失败行数：' . $e->getLine(), 'error');
            Db::rollback();
            throw new ApiException('用户注册失败', 200, 0);
        }
        return $res;
    }

    /**
     * 微博登录
     * @param $param
     * @return array|Json
     * @throws ApiException
     * @throws GuzzleException
     * @throws Exception
     */
    private function weibo($param)
    {
        $WbModel = new AppWeibo();
        // 获取用户weibo信息
        $wb_user = $WbModel->WeiboInfo($param);
        // 验证是否注册过
        $User = (new AppWeibo())->getUserWbInfoFromCache($param['uid']);
        if ($User)
        {
            return $this->LoginUser($User, $wb_user);
        }
        $checkResult = Util::registCheckLogoutCD(['wuid' => $param['uid']]);
        if (!$checkResult['result'])
        {
            throw new ApiException($checkResult['msg'], 200, config('code.error'));
        }
        Db::startTrans();
        try
        {
            // 新增用户，qq和user表进行数据添加
            $res = $this->RegUser($wb_user, 3);
            $wb_insert = [
                'uid' => $res['id'],
                'wbuid' => $param['uid'],
                'create_time' => time(),
                'nickname' => $wb_user['name'],
                'sex' => $wb_user['sex'],
                'avatar' => $wb_user['profile_image_url'],
            ];
            //wb表生成数据
            (new AppWeibo())->SaveData($wb_insert);
            Db::commit();
        }
        catch (Exception $e)
        {
            Log::write('用户注册失败：' . $e->getMessage() . '请求参数：' . json_encode($param, true) . '失败行数：' . $e->getLine(), 'error');
            Db::rollback();
            throw new ApiException('用户注册失败', 200, 0);
        }
        return $res;
    }

    /**
     * 用户注册
     * @param $user_info
     * @param $type 1微信 2QQ 3微博
     * @param string $code 身份标识
     * @return array
     * @throws ApiException
     * @throws GuzzleException
     * @throws Exception
     */
    private function RegUser($user_info, $type, $code = '')
    {
        $time = $this->request->time();

        $ip = $this->request->ip();

        $token_info = $this->getUserTokenInfo($user_info);

        if ($user_info['headimgurl'])
        {
            $avatarInfo = Util::wxImgUploadOss($user_info['headimgurl'], 'uploads/avatar');
        } else
        {
            $avatarInfo = [
                'success' => true,
                'url' => ''
            ];
        }

        if ($avatarInfo['success'] == false)
        {
            Log::write('上传三方头像失败:' . $avatarInfo['message'], 'error');
        }

        $avatar = $avatarInfo['success'] ? $avatarInfo['url'] : $user_info['headimgurl'];

        $user = [
            'uen' => substr(md5(microtime() . random_int(0, 1000000) . uniqid('', false)), 0, 12),
            'nickname' => $user_info['nickname'],
            'avatar' => $avatar,
            'sex' => $user_info['sex'],
            'reg_time' => $time,
            'reg_ip' => $ip,
            'last_login' => $time,
            'last_ip' => $ip,
            'last_active' => $time,
            'is_first_login' => 1,// 首次登陆标记
            'uc_id' => md5($user_info['unionid'] . microtime()),
            'token' => $token_info['token'],
            'token_expire' => $token_info['token_expire'],
            'email' => null,
            'forbidden' => 0,
            'has_self_recode' => 0,
            'channel' => Util::getChannelId()
        ];
        $user['id'] = (new User())->insertGetId($user);
        $msg = Db::table('app_message_center')->field('id')->where('range', 0)->select();
        foreach ($msg as $k => $v)
        {
            Db::table('app_user_msg')->insert(['uid' => $user['id'], 'mid' => $v['id']]);
        }

        // 记录注册日志
        Util::logRealTimeAnalyzeWrite(1, [
            'user_uid' => $user['id'],
            'user_name' => $user_info['nickname'],
            'user_sex' => $user_info['sex'],
            'user_reg_type' => $type,//$type 1微信 2QQ 3微博
            'user_ip' => $ip
        ]);

        if (in_array($type, [1, 2], true))
        {
            $xzNum = (new ZlUserZx())->getZxNum($type, $code);
            if ($xzNum < 1)
            {
                $hasRecord = (new ZlRecordShareAdd())->registAddRecord($avatar, $user_info['nickname'], $user['id'], $type, $code);
                if ($hasRecord)
                {
                    (new User())->save(['has_self_recode' => 1], ['id' => $user['id']]);
                    $user['has_self_recode'] = 1;  // 有档案
                }
            }
        }

        // 判断用户 APP版本大于等于 2.4.0 赠送 新手礼包
        if (Util::CheckAppVersion('2.4.0') !== '小')
        {   // 不小于 2.4.0
            Util::queuePush('register_package', ['uid' => (int)$user['id']], 0);
        }

        return [
            'id' => (int)$user['id'],
            'has_self_recode' => (int)$user['has_self_recode'],
            'user_access_token' => (new Aes())->encryptWithOpenssl($token_info['token'] . '||' . $user['id']),
            'uc_id' => $user['uc_id'],
        ];
    }

    /**
     * 用户登录
     * @param $user
     * @param $user_info
     * @return array|Json
     * @throws GuzzleException
     * @throws ApiException
     */
    private function LoginUser($user, $user_info)
    {
        if (!isset($user['forbidden']) || $user['forbidden'] == 1)
        {
            return show(config('code.error'), '该账号禁止登录');
        }
        $UserModel = new User();
        try
        {
            $token_info = $this->getUserTokenInfo($user_info);

            if (empty($user['avatar']) || preg_match('/^http(s?):\/\/*/', $user['avatar']))
            {
                $avatarInfo = Util::wxImgUploadOss($user_info['headimgurl'], 'uploads/avatar');
                if ($avatarInfo['success'])
                {
                    $token_info['avatar'] = $avatarInfo['url'];
                    Cache::rm('userinfo:' . $user['uid']);
                    // 更新档案信息
                    $recordModel = new \app\common\cachemodel\Recode();
                    $rid = $recordModel->where('uid', $user['uid'])->where('isself', 1)->value('id');
                    $recordModel->UpdateData($user['uid'], $rid, ['avatar' => $avatarInfo['url']]);
                }
            }

            $UserModel->save($token_info, ['id' => $user['uid']]);
        }
        catch (Exception $e)
        {
            Log::write($user['uid'] . '用户登录失败：' . $e->getMessage() . '失败行数：' . $e->getLine(), 'error');
            throw new ApiException('用户登录失败', 200, 0);
        }
        $userInfo = $UserModel->getUserinfo(['id' => $user['uid']]);
        //检测用户准了号
        $UserModel->check_uen($userInfo);
        return [
            'id' => (int)$user['uid'],
            'has_self_recode' => (int)$userInfo['has_self_recode'],
            'user_access_token' => (new Aes())->encryptWithOpenssl($token_info['token'] . '||' . $user['uid']),
            'uc_id' => $userInfo['uc_id'],
        ];
    }

    /**
     * 获取用户token信息
     * @param $user_info
     * @return array
     */
    private function getUserTokenInfo($user_info)
    {
        // 生成token  和 token到期时间
        $token = IAuth::setAppLoginToken($user_info['unionid']);
        $data = [
            'token' => $token,
            'token_expire' => strtotime('+' . config('app.login_time_out_day') . ' days'),
        ];
        return $data;
    }

    /**
     * 记录用户的登陆日志
     * @param $user
     * @param $type 1=>微信，2=>qq,3=>微博 4=>手机号
     * @return bool
     */
    private function LoginLog($uid, $type)
    {
        $time = $this->request->time();
        $ip = $this->request->ip();
        // 记录登陆日志  改成异步操作
        $insert = [
            'uid' => $uid,
            'login_time' => $time,
            'login_type' => $type,
            'login_ip' => $ip,
        ];
        try
        {
            (new AppLoginLog())->SaveData($insert);
        }
        catch (Exception $e)
        {
            Log::write('记录用户登录失败：' . $e->getMessage() . '失败行数：' . $e->getLine(), 'error');
            return false;
        }

        return true;
    }

    /**
     * 获取手机验证码
     * @return Json
     * @throws Exception
     */
    public function getCode()
    {
        $type = input('param.type/d', 0);
        $tel = input('param.tel/s', '');
        $alidayu = (new AppConfig())->configInfo('alidayu');
        $codeConfig = $alidayu['login_code'];

        Util::checkGetMsgCodeRequest($this->request);

        switch ($type)
        {
            case 1:
                // 检验手机号是否注册过，注册过通过
                if (!$tel || !preg_match("/^1[3456789]\d{9}$/", $tel))
                {
                    return show(config('code.error'), '手机号错误');
                }
                /**
                 * 过滤非法获取验证码请求，直接返回发送成功
                 */
                if (Util::isIllegalRequest(true))
                {
                    return show(config('code.success'), '发送成功');
                }
                // try
                // {
                //     (new User)->where(['tel' => $tel])->findOrFail();
                // }
                // catch (Exception $e)
                // {
                //     return show(409, '手机号未注册，请用三方登录');
                // }
                break;
            case 2:
                // 检验手机号是否注册过，未注册过通过
                if (!$tel || !preg_match("/^1[3456789]\d{9}$/", $tel))
                {
                    return show(config('code.error'), '手机号错误');
                }
                if (!$this->user)
                {
                    return show(config('code.denied'), '登录失效');
                }
                try
                {
                    $user = (new User)->where(['tel' => $tel])->findOrFail();
                }
                catch (Exception $e)
                {
                    $user = [];
                }
                if ($user)
                {
                    return show(config('code.error'), '此手机号已注册，请使用此手机号登录或注销此手机号重新绑定');
                }
                break;
            case 3:
                // 服务器获取手机号，若有则通过
                $codeConfig = $alidayu['logout_code'];
                if (!$this->user)
                {
                    return show(config('code.denied'), '登录失效');
                }
                if ($this->user['tel'])
                {
                    $tel = $this->user['tel'];
                } else
                {
                    return show(config('code.error'), '您未绑定手机号');
                }
                break;
            default:
                return show(config('code.error'), '未知的登录方式');
                break;
        }

        $time = time();// 当前时间  有效时间暂定为十分钟 600s

        $expire = 600;

        $ip = $this->request->ip();

        try
        {
            $data = (new AppTelLogin())->where(['tel' => (string)$tel, 'expire_time' => ['>', $time]])->findOrFail()->toArray();
        }
        catch (Exception $e)
        {
            $data = [];
        }
        // 十分钟内无已有验证码
        if (empty($data))
        {
            $code = random_int(100000, 999999);// 六位验证码

            $expire_time = $time + $expire;
            try
            {
                Db::table('app_tel_login')->insertGetId([
                    'tel' => "$tel",
                    'create_time' => $time,
                    'expire_time' => $expire_time,
                    'code' => $code,
                ]);
            }
            catch (Exception $e)
            {
                Log::write('获取手机验证码失败：' . $e->getMessage() . PHP_EOL . '失败行数：' . $e->getLine() . PHP_EOL . '错误文件：【' . $e->getFile() . '】');
                return show(config('code.error'), '发送失败');
            }

        } else
        {
            $code = $data['code'];
        }
        $result = Alidayu::getInstance()->sendSms($tel, $code, $codeConfig);
        if (!$result)
        {
            return show(config('code.error'), '发送失败');
        }
        Cache::set($this->user['id'] . 'logout', $code, 600);
        // 记录短信日志
        model('log')->save([
            'tel' => "$tel",
            'ip' => $ip,
            'create_time' => $time,
            'type' => $type,
        ]);
        return show(config('code.success'), '发送成功');
    }
}
